/**
 * \file test_simd_interface.cpp
 * 
 * \section COPYRIGHT
 *
 * Basis: A 3D Mathematics Library
 *
 * ---------------------------------------------------------------------
 *
 * Copyright (c) 2010, Don Olmstead
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. Neither the name of organization nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "basis_test.hpp"

/**
 * Contains test values to check conformance to the SIMD interface.
 */
struct SimdTestValues
{
	/// A real number type
	typedef simd_type::value_type value_type;
	/// Mask containing the results of a comparison
	typedef int comparison_mask;

	/**
	 * How close the values need to be to be considered equal.
	 *
	 * Used to compare floating point values.
	 */
	const value_type tolerance;

	/// An array containing a scalar.
	value_type scalar[4];
	/// An array containing some values.
	value_type values1[4];
	/// An array containing some values.
	value_type values2[4];

	/**
	 * Initializes the test values.
	 */
	SimdTestValues()
	: tolerance((value_type)0.001)
	{
		// Initialize a scalar value
		scalar[0] = (value_type)2;
		scalar[1] = (value_type)2;
		scalar[2] = (value_type)2;
		scalar[3] = (value_type)2;

		// Initialize some vector arrays
		values1[0] = (value_type)1;
		values1[1] = (value_type)2;
		values1[2] = (value_type)3;
		values1[3] = (value_type)4;

		values2[0] = (value_type)9;
		values2[1] = (value_type)8;
		values2[2] = (value_type)7;
		values2[3] = (value_type)6;
	}

} ; // end struct SimdTestValues

SUITE(SimdInterface)
{

	//------------------------------------------------------------
	// Constructor tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, SingleConstructor)
	{
		simd_type test(scalar[0]);

		CHECK_ARRAY_CLOSE(scalar, test, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, MultipleConstructor)
	{
		simd_type test(values1[0], values1[1], values1[2], values1[3]);

		CHECK_ARRAY_CLOSE(values1, test, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, ArrayConstructor)
	{
		simd_type test(values1);

		CHECK_ARRAY_CLOSE(values1, test, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, CopyConstructor)
	{
		simd_type copy(values1);
		simd_type test(copy);

		CHECK_ARRAY_CLOSE(values1, copy, 4, tolerance);
	}

	//------------------------------------------------------------
	// Comparison operators
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, EqualTest)
	{
		simd_type lhs(values1);
		simd_type rhs;

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] : lhs[0] - (value_type)1;
			rhs[1] = (i & simd_type::true_1) ? lhs[1] : lhs[1] - (value_type)1;
			rhs[2] = (i & simd_type::true_2) ? lhs[2] : lhs[2] - (value_type)1;
			rhs[3] = (i & simd_type::true_3) ? lhs[3] : lhs[3] - (value_type)1;

			CHECK(simd_type::equal(lhs, rhs) == i);
		}
	}

	TEST_FIXTURE(SimdTestValues, NotEqualTest)
	{
		simd_type lhs(values1);
		simd_type rhs;

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] - (value_type)1 : lhs[0];
			rhs[1] = (i & simd_type::true_1) ? lhs[1] - (value_type)1 : lhs[1];
			rhs[2] = (i & simd_type::true_2) ? lhs[2] - (value_type)1 : lhs[2];
			rhs[3] = (i & simd_type::true_3) ? lhs[3] - (value_type)1 : lhs[3];

			CHECK(simd_type::not_equal(lhs, rhs) == i);
		}
	}
	
	TEST_FIXTURE(SimdTestValues, LessThanTest)
	{
		simd_type lhs(values1);
		simd_type rhs;

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] + (value_type)1 : lhs[0] - (value_type)1;
			rhs[1] = (i & simd_type::true_1) ? lhs[1] + (value_type)1 : lhs[1] - (value_type)1;
			rhs[2] = (i & simd_type::true_2) ? lhs[2] + (value_type)1 : lhs[2] - (value_type)1;
			rhs[3] = (i & simd_type::true_3) ? lhs[3] + (value_type)1 : lhs[3] - (value_type)1;

			CHECK(simd_type::less_than(lhs, rhs) == i);
		}
	}

	TEST_FIXTURE(SimdTestValues, LessThanEqualTest)
	{
		simd_type lhs(values1);
		simd_type rhs(values1);

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		CHECK(simd_type::less_than_equal(lhs, rhs) == all);

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] + (value_type)1 : lhs[0] - (value_type)1;
			rhs[1] = (i & simd_type::true_1) ? lhs[1] + (value_type)1 : lhs[1] - (value_type)1;
			rhs[2] = (i & simd_type::true_2) ? lhs[2] + (value_type)1 : lhs[2] - (value_type)1;
			rhs[3] = (i & simd_type::true_3) ? lhs[3] + (value_type)1 : lhs[3] - (value_type)1;

			CHECK(simd_type::less_than_equal(lhs, rhs) == i);
		}
	}
	
	TEST_FIXTURE(SimdTestValues, GreaterThanTest)
	{
		simd_type lhs(values1);
		simd_type rhs;

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] - (value_type)1 : lhs[0] + (value_type)1;
			rhs[1] = (i & simd_type::true_1) ? lhs[1] - (value_type)1 : lhs[1] + (value_type)1;
			rhs[2] = (i & simd_type::true_2) ? lhs[2] - (value_type)1 : lhs[2] + (value_type)1;
			rhs[3] = (i & simd_type::true_3) ? lhs[3] - (value_type)1 : lhs[3] + (value_type)1;

			CHECK(simd_type::greater_than(lhs, rhs) == i);
		}
	}

	TEST_FIXTURE(SimdTestValues, GreaterThanEqualTest)
	{
		simd_type lhs(values1);
		simd_type rhs(values1);

		const comparison_mask all = simd_type::true_0 | simd_type::true_1 | simd_type::true_2 | simd_type::true_3;

		CHECK(simd_type::greater_than_equal(lhs, rhs) == all);

		for (comparison_mask i = 0; i < all; ++i)
		{
			rhs[0] = (i & simd_type::true_0) ? lhs[0] - (value_type)1 : lhs[0] + (value_type)1;
			rhs[1] = (i & simd_type::true_1) ? lhs[1] - (value_type)1 : lhs[1] + (value_type)1;
			rhs[2] = (i & simd_type::true_2) ? lhs[2] - (value_type)1 : lhs[2] + (value_type)1;
			rhs[3] = (i & simd_type::true_3) ? lhs[3] - (value_type)1 : lhs[3] + (value_type)1;

			CHECK(simd_type::greater_than_equal(lhs, rhs) == i);
		}
	}

	//------------------------------------------------------------
	// Element access tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, ElementAssign)
	{
		simd_type test(values1);

		test[0] = values2[0];
		test[1] = values2[1];
		test[2] = values2[2];
		test[3] = values2[3];

		CHECK_ARRAY_CLOSE(values2, test, 4, tolerance);
	}

	TEST(SimdSize)
	{
		CHECK(simd_type::size() == 4);
		CHECK(simd_type::capacity() == 4);
	}

	//------------------------------------------------------------
	// Arithmetic operator tests
	//------------------------------------------------------------
	
	TEST_FIXTURE(SimdTestValues, NegateOperator)
	{
		value_type expected[] =
		{
			-values1[0],
			-values1[1],
			-values1[2],
			-values1[3]
		} ;

		simd_type test(values1);
		simd_type negate = -test;

		CHECK_ARRAY_CLOSE(expected, negate, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, AddOperator)
	{
		value_type expected[] =
		{
			values1[0] + values2[0],
			values1[1] + values2[1],
			values1[2] + values2[2],
			values1[3] + values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);

		// Check + operator
		simd_type add = lhs + rhs;

		CHECK_ARRAY_CLOSE(expected, add, 4, tolerance);
		
		// Check += operator
		lhs += rhs;

		CHECK_ARRAY_CLOSE(expected, lhs, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, SubOperator)
	{
		value_type expected[] =
		{
			values1[0] - values2[0],
			values1[1] - values2[1],
			values1[2] - values2[2],
			values1[3] - values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);

		// Check - operator
		simd_type sub = lhs - rhs;

		CHECK_ARRAY_CLOSE(expected, sub, 4, tolerance);
		
		// Check -= operator
		lhs -= rhs;

		CHECK_ARRAY_CLOSE(expected, lhs, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, MulOperator)
	{
		value_type expected[] =
		{
			values1[0] * values2[0],
			values1[1] * values2[1],
			values1[2] * values2[2],
			values1[3] * values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);

		// Check * operator
		simd_type mul = lhs * rhs;

		CHECK_ARRAY_CLOSE(expected, mul, 4, tolerance);
		
		// Check *= operator
		lhs *= rhs;

		CHECK_ARRAY_CLOSE(expected, lhs, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, DivOperator)
	{
		value_type expected[] =
		{
			values1[0] / values2[0],
			values1[1] / values2[1],
			values1[2] / values2[2],
			values1[3] / values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);

		// Check / operator
		simd_type div = lhs / rhs;

		CHECK_ARRAY_CLOSE(expected, div, 4, tolerance);
		
		// Check /= operator
		lhs /= rhs;

		CHECK_ARRAY_CLOSE(expected, lhs, 4, tolerance);
	}

	//------------------------------------------------------------
	// Arithmetic tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, InvValues)
	{
		value_type expected[] =
		{
			(value_type)1 / values1[0],
			(value_type)1 / values1[1],
			(value_type)1 / values1[2],
			(value_type)1 / values1[3]
		} ;

		simd_type test(values1);
		simd_type inv = simd_type::inv(test);

		CHECK_ARRAY_CLOSE(expected, inv, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, SqrtValues)
	{
		value_type expected[] =
		{
			std::sqrt(values1[0]),
			std::sqrt(values1[1]),
			std::sqrt(values1[2]),
			std::sqrt(values1[3])
		} ;

		simd_type test(values1);
		simd_type sqrt = simd_type::sqrt(test);

		CHECK_ARRAY_CLOSE(expected, sqrt, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, InvSqrtValues)
	{
		value_type expected[] =
		{
			(value_type)1 / std::sqrt(values1[0]),
			(value_type)1 / std::sqrt(values1[1]),
			(value_type)1 / std::sqrt(values1[2]),
			(value_type)1 / std::sqrt(values1[3])
		} ;

		simd_type test(values1);
		simd_type inv_sqrt = simd_type::inv_sqrt(test);

		CHECK_ARRAY_CLOSE(expected, inv_sqrt, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, MinValues)
	{
		value_type expected[] =
		{
			(values1[0] < values2[0]) ? values1[0] : values2[0],
			(values1[1] < values2[1]) ? values1[1] : values2[1],
			(values1[2] < values2[2]) ? values1[2] : values2[2],
			(values1[3] < values2[3]) ? values1[3] : values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type min = simd_type::min(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, min, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, MaxValues)
	{
		value_type expected[] =
		{
			(values1[0] > values2[0]) ? values1[0] : values2[0],
			(values1[1] > values2[1]) ? values1[1] : values2[1],
			(values1[2] > values2[2]) ? values1[2] : values2[2],
			(values1[3] > values2[3]) ? values1[3] : values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type max = simd_type::max(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, max, 4, tolerance);
	}

	//------------------------------------------------------------
	// Horizontal arithmetic tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, HorizontalAdd)
	{
		value_type expected[] =
		{
			values1[0] + values1[1],
			values1[2] + values1[3],
			values2[0] + values2[1],
			values2[2] + values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type add = simd_type::hadd(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, add, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, HorizontalSub)
	{
		value_type expected[] =
		{
			values1[0] - values1[1],
			values1[2] - values1[3],
			values2[0] - values2[1],
			values2[2] - values2[3]
		} ;

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type sub = simd_type::hsub(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, sub, 4, tolerance);
	}

	//------------------------------------------------------------
	// Vector operation tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, DotProduct2)
	{
		value_type expected_dot =
			values1[0] * values2[0] +
			values1[1] * values2[1];

		value_type expected[] = { expected_dot, expected_dot, expected_dot, expected_dot };

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type dot = simd_type::dot2(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, dot, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, DotProduct3)
	{
		value_type expected_dot =
			values1[0] * values2[0] +
			values1[1] * values2[1] +
			values1[2] * values2[2];

		value_type expected[] = { expected_dot, expected_dot, expected_dot, expected_dot };

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type dot = simd_type::dot3(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, dot, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, DotProduct4)
	{
		value_type expected_dot =
			values1[0] * values2[0] +
			values1[1] * values2[1] +
			values1[2] * values2[2] +
			values1[3] * values2[3];

		value_type expected[] = { expected_dot, expected_dot, expected_dot, expected_dot };

		simd_type lhs(values1);
		simd_type rhs(values2);
		simd_type dot = simd_type::dot4(lhs, rhs);

		CHECK_ARRAY_CLOSE(expected, dot, 4, tolerance);
	}
	
	//------------------------------------------------------------
	// Shuffle tests
	//------------------------------------------------------------

	TEST_FIXTURE(SimdTestValues, ShuffleStatic)
	{
		// Create the simd_type
		simd_type lhs(values1);
		simd_type rhs(values2);

		// Create the shuffles
		simd_type shuffle_xyzw = simd_type::shuffle<0, 1, 2, 3>(lhs, rhs);
		simd_type shuffle_yzwx = simd_type::shuffle<1, 2, 3, 0>(lhs, rhs);
		simd_type shuffle_zwxy = simd_type::shuffle<2, 3, 0, 1>(lhs, rhs);
		simd_type shuffle_wxyz = simd_type::shuffle<3, 0, 1, 2>(lhs, rhs);

		// Create the arrays
		value_type expected_xyzw[] = { values1[0], values1[1], values2[2], values2[3] };
		value_type expected_yzwx[] = { values1[1], values1[2], values2[3], values2[0] };
		value_type expected_zwxy[] = { values1[2], values1[3], values2[0], values2[1] };
		value_type expected_wxyz[] = { values1[3], values1[0], values2[1], values2[2] };

		CHECK_ARRAY_CLOSE(expected_xyzw, shuffle_xyzw, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_yzwx, shuffle_yzwx, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_zwxy, shuffle_zwxy, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_wxyz, shuffle_wxyz, 4, tolerance);
	}

	TEST_FIXTURE(SimdTestValues, ShuffleMethod)
	{
		// Create the simd_type
		simd_type test(values1);

		// Create the shuffles
		simd_type shuffle_xyzw = test.shuffle<0, 1, 2, 3>();
		simd_type shuffle_yzwx = test.shuffle<1, 2, 3, 0>();
		simd_type shuffle_zwxy = test.shuffle<2, 3, 0, 1>();
		simd_type shuffle_wxyz = test.shuffle<3, 0, 1, 2>();

		// Create the arrays
		value_type expected_xyzw[] = { values1[0], values1[1], values1[2], values1[3] };
		value_type expected_yzwx[] = { values1[1], values1[2], values1[3], values1[0] };
		value_type expected_zwxy[] = { values1[2], values1[3], values1[0], values1[1] };
		value_type expected_wxyz[] = { values1[3], values1[0], values1[1], values1[2] };

		CHECK_ARRAY_CLOSE(expected_xyzw, shuffle_xyzw, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_yzwx, shuffle_yzwx, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_zwxy, shuffle_zwxy, 4, tolerance);
		CHECK_ARRAY_CLOSE(expected_wxyz, shuffle_wxyz, 4, tolerance);
	}

} // end SUITE(SimdInterface)
